namespace System.Threading.Tasks
{
    public class TaskFactory<TResult>
    {
        private readonly TaskScheduler scheduler;
        private CancellationToken cancellationToken;
        private TaskContinuationOptions continuationOptions;
        private TaskCreationOptions creationOptions;
        private TaskFactory parent;

        public TaskFactory()
            : this(CancellationToken.None)
        {
        }

        public TaskFactory(TaskScheduler scheduler)
            : this(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, scheduler)
        {
        }

        public TaskFactory(CancellationToken cancellationToken)
            : this(cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, null)
        {
        }

        public TaskFactory(TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions)
            : this(CancellationToken.None, creationOptions, continuationOptions, null)
        {
        }

        public TaskFactory(CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
        {
            this.cancellationToken = cancellationToken;
            this.scheduler = scheduler;
            this.creationOptions = creationOptions;
            this.continuationOptions = continuationOptions;

            TaskFactory.CheckContinuationOptions(continuationOptions);

            this.parent = new TaskFactory(cancellationToken, creationOptions, continuationOptions, scheduler);
        }

        public CancellationToken CancellationToken
        {
            get
            {
                return cancellationToken;
            }
        }

        public TaskContinuationOptions ContinuationOptions
        {
            get
            {
                return continuationOptions;
            }
        }

        public TaskCreationOptions CreationOptions
        {
            get
            {
                return creationOptions;
            }
        }

        public TaskScheduler Scheduler
        {
            get
            {
                return scheduler;
            }
        }

        public Task<TResult> ContinueWhenAll(Task[] tasks, Func<Task[], TResult> continuationFunction)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll(Task[] tasks,
                                              Func<Task[], TResult> continuationFunction,
                                              TaskContinuationOptions continuationOptions)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll(Task[] tasks,
                                              Func<Task[], TResult> continuationFunction,
                                              CancellationToken cancellationToken)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll(Task[] tasks,
                                              Func<Task[], TResult> continuationFunction,
                                              CancellationToken cancellationToken,
                                              TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
        {
            return parent.ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, scheduler);
        }

        public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>[], TResult> continuationFunction)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>[], TResult> continuationFunction,
                                                                 TaskContinuationOptions continuationOptions)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>[], TResult> continuationFunction,
                                                                 CancellationToken cancellationToken)
        {
            return ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>[], TResult> continuationFunction,
                                                                 CancellationToken cancellationToken,
                                                                 TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
        {
            return parent.ContinueWhenAll(tasks, continuationFunction, cancellationToken, continuationOptions, scheduler);
        }

        public Task<TResult> ContinueWhenAny(Task[] tasks,
                                              Func<Task, TResult> continuationFunction)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny(Task[] tasks,
                                              Func<Task, TResult> continuationFunction,
                                              CancellationToken cancellationToken)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny(Task[] tasks,
                                              Func<Task, TResult> continuationFunction,
                                              TaskContinuationOptions continuationOptions)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny(Task[] tasks,
                                              Func<Task, TResult> continuationFunction,
                                              CancellationToken cancellationToken,
                                              TaskContinuationOptions continuationOptions,
                                              TaskScheduler scheduler)
        {
            return parent.ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, scheduler);
        }

        public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>, TResult> continuationFunction)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>, TResult> continuationFunction,
                                                                 CancellationToken cancellationToken)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>, TResult> continuationFunction,
                                                                 TaskContinuationOptions continuationOptions)
        {
            return ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, GetScheduler());
        }

        public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks,
                                                                 Func<Task<TAntecedentResult>, TResult> continuationFunction,
                                                                 CancellationToken cancellationToken,
                                                                 TaskContinuationOptions continuationOptions,
                                                                 TaskScheduler scheduler)
        {
            return parent.ContinueWhenAny(tasks, continuationFunction, cancellationToken, continuationOptions, scheduler);
        }

        public Task<TResult> FromAsync(IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod)
        {
            return FromAsync(asyncResult, endMethod, creationOptions);
        }

        public Task<TResult> FromAsync(IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod, TaskCreationOptions creationOptions)
        {
            return FromAsync(asyncResult, endMethod, creationOptions, GetScheduler());
        }

        public Task<TResult> FromAsync(IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod, TaskCreationOptions creationOptions, TaskScheduler scheduler)
        {
            return FromIAsyncResult(asyncResult, endMethod, creationOptions, scheduler);
        }

        public Task<TResult> FromAsync(Func<AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                        object state)
        {
            return FromAsync(beginMethod, endMethod, state, creationOptions);
        }

        public Task<TResult> FromAsync(Func<AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                        object state, TaskCreationOptions creationOptions)
        {
            return FromAsyncBeginEnd(beginMethod, endMethod, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                               TArg1 arg1, object state)
        {
            return FromAsync(beginMethod, endMethod, arg1, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                               TArg1 arg1, object state, TaskCreationOptions creationOptions)
        {
            return FromAsyncBeginEnd(beginMethod, endMethod, arg1, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1, TArg2>(Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                                      TArg1 arg1, TArg2 arg2, object state)
        {
            return FromAsync(beginMethod, endMethod, arg1, arg2, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1, TArg2>(Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                                      TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions)
        {
            return FromAsyncBeginEnd(beginMethod, endMethod, arg1, arg2, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                                             TArg1 arg1, TArg2 arg2, TArg3 arg3, object state)
        {
            return FromAsync(beginMethod, endMethod, arg1, arg2, arg3, state, creationOptions);
        }

        public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                                             TArg1 arg1, TArg2 arg2, TArg3 arg3, object state,
                                                             TaskCreationOptions creationOptions)
        {
            return FromAsyncBeginEnd(beginMethod, endMethod, arg1, arg2, arg3, state, creationOptions);
        }

        public Task<TResult> StartNew(Func<TResult> function)
        {
            return StartNew(function, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<TResult> function, TaskCreationOptions creationOptions)
        {
            return StartNew(function, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<TResult> function, CancellationToken cancellationToken)
        {
            return StartNew(function, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<TResult> function,
                                       CancellationToken cancellationToken,
                                       TaskCreationOptions creationOptions,
                                       TaskScheduler scheduler)
        {
            return StartNew((o) => function(), null, cancellationToken, creationOptions, scheduler);
        }

        public Task<TResult> StartNew(Func<object, TResult> function, object state)
        {
            return StartNew(function, state, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<object, TResult> function, object state, TaskCreationOptions creationOptions)
        {
            return StartNew(function, state, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<object, TResult> function, object state, CancellationToken cancellationToken)
        {
            return StartNew(function, state, cancellationToken, creationOptions, GetScheduler());
        }

        public Task<TResult> StartNew(Func<object, TResult> function, object state,
                                       CancellationToken cancellationToken,
                                       TaskCreationOptions creationOptions,
                                       TaskScheduler scheduler)
        {
            return parent.StartNew<TResult>(function, state, cancellationToken, creationOptions, scheduler);
        }

        internal static Task<TResult> FromAsyncBeginEnd(Func<AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                                         object state, TaskCreationOptions creationOptions)
        {
            if (beginMethod == null)
                throw new ArgumentNullException("beginMethod");

            if (endMethod == null)
                throw new ArgumentNullException("endMethod");

            if ((creationOptions & Task.WorkerTaskNotSupportedOptions) != 0)
                throw new ArgumentOutOfRangeException("creationOptions");

            var tcs = new TaskCompletionSource<TResult>(state, creationOptions);
            var alreadyInvoked = new AtomicBoolean();
            var iar = beginMethod(l =>
            {
                if (alreadyInvoked.TryRelaxedSet())
                    InnerInvoke(tcs, endMethod, l);
            }, state);
            if (iar != null && iar.CompletedSynchronously && alreadyInvoked.TryRelaxedSet())
                InnerInvoke(tcs, endMethod, iar);

            return tcs.Task;
        }

        internal static Task<TResult> FromAsyncBeginEnd<TArg1>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod,
                                                                Func<IAsyncResult, TResult> endMethod,
                                                                TArg1 arg1, object state, TaskCreationOptions creationOptions)
        {
            if (beginMethod == null)
                throw new ArgumentNullException("beginMethod");

            if (endMethod == null)
                throw new ArgumentNullException("endMethod");

            if ((creationOptions & Task.WorkerTaskNotSupportedOptions) != 0)
                throw new ArgumentOutOfRangeException("creationOptions");

            var tcs = new TaskCompletionSource<TResult>(state, creationOptions);
            var alreadyInvoked = new AtomicBoolean();
            var iar = beginMethod(arg1, l =>
            {
                if (alreadyInvoked.TryRelaxedSet())
                    InnerInvoke(tcs, endMethod, l);
            }, state);
            if (iar != null && iar.CompletedSynchronously && alreadyInvoked.TryRelaxedSet())
                InnerInvoke(tcs, endMethod, iar);

            return tcs.Task;
        }

        internal static Task<TResult> FromAsyncBeginEnd<TArg1, TArg2>(Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                          TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions)
        {
            if (beginMethod == null)
                throw new ArgumentNullException("beginMethod");

            if (endMethod == null)
                throw new ArgumentNullException("endMethod");

            if ((creationOptions & Task.WorkerTaskNotSupportedOptions) != 0)
                throw new ArgumentOutOfRangeException("creationOptions");

            var tcs = new TaskCompletionSource<TResult>(state, creationOptions);
            var alreadyInvoked = new AtomicBoolean();
            var iar = beginMethod(arg1, arg2, l =>
            {
                if (alreadyInvoked.TryRelaxedSet())
                    InnerInvoke(tcs, endMethod, l);
            }, state);
            if (iar != null && iar.CompletedSynchronously && alreadyInvoked.TryRelaxedSet())
                InnerInvoke(tcs, endMethod, iar);

            return tcs.Task;
        }

        internal static Task<TResult> FromAsyncBeginEnd<TArg1, TArg2, TArg3>(Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod,
                                          TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions)
        {
            if (beginMethod == null)
                throw new ArgumentNullException("beginMethod");

            if (endMethod == null)
                throw new ArgumentNullException("endMethod");

            if ((creationOptions & Task.WorkerTaskNotSupportedOptions) != 0)
                throw new ArgumentOutOfRangeException("creationOptions");

            var tcs = new TaskCompletionSource<TResult>(state, creationOptions);
            var alreadyInvoked = new AtomicBoolean();
            var iar = beginMethod(arg1, arg2, arg3, l =>
            {
                if (alreadyInvoked.TryRelaxedSet())
                    InnerInvoke(tcs, endMethod, l);
            }, state);
            if (iar != null && iar.CompletedSynchronously && alreadyInvoked.TryRelaxedSet())
                InnerInvoke(tcs, endMethod, iar);

            return tcs.Task;
        }

        internal static Task<TResult> FromIAsyncResult(IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod, TaskCreationOptions creationOptions, TaskScheduler scheduler)
        {
            if (asyncResult == null)
                throw new ArgumentNullException("asyncResult");

            if (endMethod == null)
                throw new ArgumentNullException("endMethod");

            if (scheduler == null)
                throw new ArgumentNullException("scheduler");

            if ((creationOptions & Task.WorkerTaskNotSupportedOptions) != 0)
                throw new ArgumentOutOfRangeException("creationOptions");

            var source = new CancellationTokenSource();
            var task = new Task<TResult>(l =>
            {
                try
                {
                    return endMethod(asyncResult);
                }
                catch (OperationCanceledException)
                {
                    source.Cancel();
                    source.Token.ThrowIfCancellationRequested();
                }
                return default(TResult);
            }, null, source.Token, creationOptions);

            if (asyncResult.IsCompleted)
            {
                task.RunSynchronously(scheduler);
            }
            else
            {
                ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle,
                                                        (s, t) => task.RunSynchronously(scheduler),
                                                        null, Timeout.Infinite, true);
            }

            return task;
        }

        private static void InnerInvoke(TaskCompletionSource<TResult> tcs, Func<IAsyncResult, TResult> endMethod, IAsyncResult l)
        {
            try
            {
                tcs.SetResult(endMethod(l));
            }
            catch (OperationCanceledException)
            {
                tcs.SetCanceled();
            }
            catch (Exception e)
            {
                tcs.SetException(e);
            }
        }

        private TaskScheduler GetScheduler()
        {
            return scheduler ?? TaskScheduler.Current;
        }
    }
}
